package com.sl.plugin.step.selenium.navigate;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.StreamProgress;
import cn.hutool.core.util.*;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.system.OsInfo;
import cn.hutool.system.SystemUtil;
import cn.hutool.system.UserInfo;
import com.sl.core.engine.meta.impl.AbstractProcessCompDescriptor;
import com.sl.core.engine.step.ProcessCompService;
import com.sl.core.engine.step.impl.AbstractProcessComp;
import com.sl.plugin.step.selenium.navigate.config.SeleniumConfig;
import io.github.bonigarcia.wdm.WebDriverManager;
import lombok.SneakyThrows;
import org.openqa.selenium.SessionNotCreatedException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeDriverService;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.support.ui.WebDriverWait;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

/**
 *
 */
public class SeleniumNavigateComp extends AbstractProcessComp<Map<String, Object>, WebDriver> implements ProcessCompService {

    //文件版本,防止多线程缓存文件和用户文件共享,导致创建错误
    private static AtomicInteger fileSerial = new AtomicInteger(0);

    public SeleniumNavigateComp(AbstractProcessCompDescriptor abstractProcessCompDescriptor) {
        super(abstractProcessCompDescriptor);
    }

//    public SeleniumNavigateComp(){
//        super(null,null,null,0,null,null);
//    }

    @Override
    public WebDriver input(Map<String, Object> object) {
//        System.setProperty("webdriver.chrome.driver", "/Users/huyi/work/project/square_lake/square-lake-plugins/square-lake-plugin-step/square-lake-plugin-step-selenium/square-lake-plugin-step-selenium-test1/src/main/resources/chromedriver");
        logBasic("现在开始获取webDriver");
//        File webDriver = getWebDriverFile();
//        logBasic("获取webDriver结束");
//        if (ObjectUtil.isNull(webDriver)) {
//            return null;
//        }
        configureLogging();
        //googlechrome.dmg
        List<String> options = meta.getListAttr("options");
        // 谷歌文档提到需要加上这个属性来规避bug
        options.add("--disable-gpu");
        //禁用3D软件光栅化器
        options.add("--disable-software-rasterizer");
        // 为了让linux root用户也能执行
        options.add("--no-sandbox");
        ////解决在某些VM环境中，/dev/shm分区太小，导致Chrome失败或崩溃
        options.add("--disable-dev-shm-usage");
        options.add("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36");
//        options.add("--headless=new");

        //无痕模式
        options.add("--incognito");
        //禁用插件,加快速度
        options.add("--disable-plugins");
        //禁用扩展
        options.add("--disable-extensions");
        //关闭弹窗拦截
        options.add("--disable-popup-blocking");
        //  禁现窗口最大化
        options.add("--ignore-certificate-errors");
        //关闭https提示 32位
        options.add("--allow-running-insecure-content");
        //禁用浏览器正在被自动化程序控制的提示  ,但是高版本不生效
        options.add("--disable-infobars");
        //随机设置请求头
//        options.add("--user-agent=" + UserAgent.getUserAgentWindows());
        WebDriverManager.chromedriver().setup();

        String tmpdir = System.getProperty("java.io.tmpdir");
        String dir = tmpdir + File.separator + "chrome_file_data_cache" + File.separator + fileSerial.incrementAndGet();
        File file1 = new File(dir + File.separator + "data");
        if (file1.exists()) {
            file1.mkdirs();
        }
        File file2 = new File(dir + File.separator + "cache");
        if (file2.exists()) {
            file1.mkdirs();
        }
        //解决打开页面出现data;空白页面情况,因为没有缓存目录
        options.add("--user-data-dir=" + file1.getAbsolutePath());
        //指定Cache路径
        options.add("--disk-cache-dir=" + file2.getAbsolutePath());

        String url = meta.getStrAttr("url");
        if (StrUtil.isBlank(url)) {
            logBasic("没有输入地址 停止执行");
            return null;
        }
        ChromeDriverService defaultService = new ChromeDriverService.Builder().build();
        ChromeOptions chromeOptions = new ChromeOptions();
        if (CollUtil.isNotEmpty(options)) {
            for (String option : options) {
                if (StrUtil.isBlank(option)) {
                    continue;
                }
                chromeOptions.addArguments(option);
                //chromeOptions.addArguments("headless");
                //chromeOptions.addArguments("window-size=1920x1080");
            }
        }
        chromeOptions.setCapability("goog:loggingPrefs", Map.of("driver", "ALL", "browser", "ALL"));
        WebDriver driver = null;
        WebDriverWait wait = null;
        try {

            driver = new ChromeDriver(defaultService, chromeOptions);
            wait = new WebDriverWait(driver, Duration.ofSeconds(120));

        } catch (SessionNotCreatedException e) {
            e.printStackTrace();
            logError("没有找到chrome浏览器，现在进行自动下载安装", e);
            //重新创建一次
            downloadGoogleChrome();
            //  input(object);
        }
        if (ObjectUtil.isNull(driver)) {
            return null;
        }
        //当我们去定位页面元素时，如果元素没有找到，不会立即抛出异常，而是周期性地（通常为 0.5s）去重新寻找，直到该元素找到或者超过最大等待时间才结束 ,超时后就报错NoTouchElementException
        //当我们使用implicitly_wait()时，如果想要定位的元素已经找到，但是它的内容（如文本内容，属性等）没有加载出来，此时隐式等待无效，仍会直接抛出NoSuchElementException异常，这也是为什么我们很多时候仍需要使用time.sleep()的原因。
        driver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS);

        driver.get(url);
//
//        //和get类似,支持前进和后退切换
//        driver.navigate().to(url);
//        //退到上一个页面 ,前提必须前进了一个页面才能回退
//        driver.navigate().back();
//        //指前进到下一个页面 ,前提是必须后退后才能前进
//        driver.navigate().forward();
//        // 刷新当前页面
//        driver.navigate().refresh();
        return driver;
    }

    @SneakyThrows
    private void downloadGoogleChrome() {
        // 判断是什么环境
        SeleniumConfig seleniumConfig = meta.get("seleniumConfig");
        File file = null;
        OsInfo osInfo = SystemUtil.getOsInfo();
        if (osInfo.isMac() || osInfo.isMacOsX()) {
            URL resource = this.getClass().getResource("/mac/googlechrome.dmg");
            if (ObjectUtil.isNull(resource)) {
                logBasic("没有在本地找到安装文件，现在开始从远程下载");
                //走下载
                //  String concat = StrUtil.concat(true, webDriverUrl, "chromedriver_mac64.zip");
                file = this.downloadGoogle(seleniumConfig.getChromeUrlConfig().getMac());
                logBasic("从远程下载完成");

            } else {
                file = FileUtil.file(resource);
                logBasic("在本地找到了安装文件");
            }
            logBasic("现在开始判断是否安装了brew");
            String str = RuntimeUtil.execForStr("/bin/bash -c brew");
            if (StrUtil.contains(str, "command not found")) {
                //需要安装brew
                logError("请安装brew,命令/bin/bash -c \"$(curl -fsSL https://gitee.com/ineo6/homebrew-install/raw/master/install.sh)\"");
                return;
            }
            logBasic("现在开始构造本地缓存");
            String s = RuntimeUtil.execForStr("brew --cache");
            s = s.replace("\n", "");//downloads
            String s2 = DigestUtil.sha256Hex(seleniumConfig.getChromeUrlConfig().getMac());
            //挪动包
            File file1 = new File(s + "/" + s2 + "--google-chrome.dmg");
            FileUtil.copy(file, file1, true);
            logBasic("构造本地缓存结束");
            logBasic("现在开始使用brew进行安装");
            String exec = RuntimeUtil.execForStr("brew install --cask google-chrome");
            logBasic("brew安装chrome结束");
        } else if (osInfo.isLinux()) {
            URL resource = this.getClass().getResource("/linux/chromedriver");
            if (ObjectUtil.isNull(resource)) {
                //走下载
                //  String concat = StrUtil.concat(true, webDriverUrl, "chromedriver_mac64.zip");
                file = this.downloadGoogle(seleniumConfig.getChromeUrlConfig().getLinux());
            } else {
                file = FileUtil.file(resource);
            }

            String s = RuntimeUtil.execForStr("apt-get install libgtk2.0-0 libgconf-2-4 libpango1.0-0 libcairo2 libcups2 libxss1 libxkbfile1");

            String s1 = RuntimeUtil.execForStr("cd " + file.getAbsolutePath() + "| dpkg -i google-chrome-stable_current_amd64.deb");


        } else if (osInfo.isWindows()) {
            URL resource = this.getClass().getResource("/windows/chromedriver.exe");
            if (ObjectUtil.isNull(resource)) {
                //走下载
                file = this.downloadGoogle(seleniumConfig.getChromeUrlConfig().getWindows());
            } else {
                file = FileUtil.file(resource);
            }
            //todo
        }

    }


    // 配置日志记录
    private static void configureLogging() {
        try {
            // 创建文件处理器
            FileHandler fileHandler = new FileHandler("webscraper.log", true);
            fileHandler.setFormatter(new SimpleFormatter());

            // 获取根日志器并添加处理器
            Logger rootLogger = Logger.getLogger("");
            rootLogger.addHandler(fileHandler);

            // 设置日志级别
            rootLogger.setLevel(Level.INFO);

        } catch (IOException e) {
            System.err.println("配置日志失败: " + e.getMessage());
        }
    }

    private File getWebDriverFile() {
        //判断是什么环境
        File file = null;
        OsInfo osInfo = SystemUtil.getOsInfo();
        logBasic("当前系统环境是" + osInfo.getName());
        if (osInfo.isMac() || osInfo.isMacOsX()) {
            URL resource = this.getClass().getResource("/mac/chromedriver");
            if (ObjectUtil.isNull(resource)) {
                logBasic("在本地文件系统中没有找到webDriver");
                SeleniumConfig seleniumConfig = meta.get("seleniumConfig");
                //走下载
                logBasic("开始下载webDriver");
                file = this.downloadWebDriver(seleniumConfig.getWebDriverUrlConfig().getMac());
                //chmod +x /Users/weixin/selenium/828768
                logBasic("下载webDriver结束");

            } else {
                logBasic("在本地文件系统中找到了webDriver");
                file = FileUtil.file(resource);
            }

        } else if (osInfo.isLinux()) {
            URL resource = this.getClass().getResource("/linux/chromedriver");
            if (ObjectUtil.isNull(resource)) {
                logBasic("在本地文件系统中没有找到webDriver");
                SeleniumConfig seleniumConfig = meta.get("seleniumConfig");
                //走下载
                logBasic("开始下载webDriver");
                file = this.downloadWebDriver(seleniumConfig.getWebDriverUrlConfig().getLinux());
                logBasic("下载webDriver结束");
            } else {

                logBasic("在本地文件系统中找到了webDriver");
                file = FileUtil.file(resource);
            }
        } else if (osInfo.isWindows()) {
            URL resource = this.getClass().getResource("/windows/chromedriver.exe");
            if (ObjectUtil.isNull(resource)) {
                SeleniumConfig seleniumConfig = meta.get("seleniumConfig");
                //走下载
                file = this.downloadWebDriver(seleniumConfig.getWebDriverUrlConfig().getWindows());
            } else {
                file = FileUtil.file(resource);
            }
        }
        return file;

    }

    @SneakyThrows
    private File downloadGoogle(String url) {

        UserInfo userInfo = SystemUtil.getUserInfo();
        String homeDir = userInfo.getHomeDir();

        long l = HashUtil.hfHash(url);

        String ff = homeDir + "/selenium/" + l + "." + FileUtil.getSuffix(url);
        File file = new File(ff);

        if (FileUtil.exist(file)) {
            return file;
        }
        file = FileUtil.mkdir(homeDir + "/selenium/temp");

        File dfile = FileUtil.file(file, "download." + FileUtil.getSuffix(url));
        FileOutputStream fileOutputStream = new FileOutputStream(dfile);
        //下载
        HttpUtil.download(url, fileOutputStream, true, new StreamProgress() {
            @Override
            public void start() {

            }

            @Override
            public void progress(long total, long progressSize) {
                logBasic("下载进度" + progressSize + "/" + total);
            }

            @Override
            public void finish() {

            }
        });

        File file1 = new File(ff);
        FileUtil.copy(dfile, file1, true);
        FileUtil.del(dfile);
        return file1;

    }

    @SneakyThrows
    private File downloadWebDriver(String url) {
        UserInfo userInfo = SystemUtil.getUserInfo();
        String homeDir = userInfo.getHomeDir();

        long l = HashUtil.hfHash(url);

        File file = new File(homeDir + "/selenium/" + l);

        if (FileUtil.exist(file)) {
            return file;
        }
        file = FileUtil.mkdir(homeDir + "/selenium/temp");

        File dfile = FileUtil.file(file, "download.zip");
        FileOutputStream fileOutputStream = new FileOutputStream(dfile);
        //下载
        HttpUtil.download(url, fileOutputStream, true, new StreamProgress() {
            @Override
            public void start() {

            }

            @Override
            public void progress(long total, long progressSize) {
                logBasic("下载进度" + progressSize + "/" + total);
            }

            @Override
            public void finish() {

            }
        });

        //
        File unzip = ZipUtil.unzip(dfile);

        File cf = FileUtil.loopFiles(unzip, (f) -> {

            String name = f.getName();
            if (StrUtil.startWith(name, "chromedriver")) {
                return true;
            }
            return false;
        }).stream().findFirst().orElse(null);
        File file1 = new File(homeDir + "/selenium/" + l);
        if (ObjectUtil.isNotNull(cf)) {
            FileUtil.copy(cf, file1, true);
            FileUtil.del(unzip);
            FileUtil.del(dfile);
        }

        return file1;

    }

}
